package com.example.controller;

import cn.hutool.core.io.FileUtil;
import com.example.common.Result;
import com.example.entity.NxSystemFileInfo;
import com.example.exception.CustomException;
import com.example.service.NxSystemFileInfoService;
import com.github.pagehelper.PageInfo;
import org.apache.commons.codec.binary.Hex;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 系统文件管理控制器
 * 提供文件上传/下载/删除、分页查询及哈希校验等核心功能
 */
@RestController
@RequestMapping("/files")
public class NxSystemFileController {

    private static final String BASE_PATH = "D:/AAA/files/";

    @Resource
    private NxSystemFileInfoService nxSystemFileInfoService;

    /**
     * 文件上传接口（支持哈希去重）
     * @param file 上传的文件对象（必填）
     * @param request HTTP请求上下文
     * @return {@link Result} 包含文件记录信息的结果对象
     * @throws IOException 文件读写异常
     * @implNote 实现流程：
     * 1. 校验文件非空 → 2. 计算SHA-256哈希值 → 3. 哈希去重校验 →
     * 4. 生成唯一文件名（哈希值+扩展名）→ 5. 保存文件到本地 →
     * 6. 构建数据库记录 → 7. 执行入库操作
     */
    @PostMapping("/upload")
    public Result upload(MultipartFile file, HttpServletRequest request) throws IOException {
        // 1. 校验文件非空
        if (file.isEmpty()) {
            return Result.error("4000", "文件不能为空");
        }

        // 2. 计算文件哈希值（使用SHA-256算法）
        String fileHash = calculateFileHash(file.getInputStream());

        // 3. 检查文件是否已存在
        NxSystemFileInfo existingFile = nxSystemFileInfoService.getByFileHash(fileHash);
        if (existingFile != null) {
            return Result.error("40001","文件已存在");
        }

        // 4. 生成唯一文件名（哈希值 + 原扩展名）
        String originName = file.getOriginalFilename();
        String fileName = fileHash + "." + FileUtil.extName(originName);

        // 5. 保存文件到本地
        FileUtil.writeBytes(file.getBytes(), BASE_PATH + fileName);

        // 6. 构建入库信息
        NxSystemFileInfo info = new NxSystemFileInfo();
        info.setOriginName(originName);
        info.setFileName(fileName);
        info.setFileHash(fileHash);

        // 7. 执行入库操作
        NxSystemFileInfo addInfo = nxSystemFileInfoService.add(info);

        return addInfo != null
                ? Result.success(addInfo)
                : Result.error("4001", "上传失败");
    }

    /**
     * 文件分页查询
     * @param name 搜索关键词（支持文件名模糊查询）
     * @param pageNum 当前页码（默认1）
     * @param pageSize 每页显示数量（默认10）
     * @return {@link Result} 包含文件分页数据的结果对象（PageInfo封装分页信息）
     */
    @GetMapping("/page/{name}")
    public Result<PageInfo<NxSystemFileInfo>> filePage(@PathVariable String name,
                                                       @RequestParam(defaultValue = "1") Integer pageNum,
                                                       @RequestParam(defaultValue = "10") Integer pageSize) {
        PageInfo<NxSystemFileInfo> pageInfo = nxSystemFileInfoService.findPage(name, pageNum, pageSize);
        return Result.success(pageInfo);
    }

    /**
     * 文件下载接口
     * @param id 文件记录ID（路径参数）
     * @param response HTTP响应对象（用于输出文件流）
     * @throws IOException 文件读写异常
     * @implNote 实现流程：
     * 1. 校验ID有效性 → 2. 查询文件记录 → 3. 校验文件存在性 →
     * 4. 设置响应头（Content-Disposition/Content-Length）→
     * 5. 输出文件流到响应体
     */
    @GetMapping("/download/{id}")
    public void download(@PathVariable String id, HttpServletResponse response) throws IOException {
        // 校验ID有效性
        if ("null".equals(id)) {
            throw new CustomException("1001", "您未上传文件");
        }

        NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
        if (nxSystemFileInfo == null) {
            throw new CustomException("1001", "未查询到该文件");
        }

        byte[] bytes = FileUtil.readBytes(BASE_PATH + nxSystemFileInfo.getFileName());
        response.reset();

        // 设置response的Header
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(nxSystemFileInfo.getOriginName(), "UTF-8"));
        response.addHeader("Content-Length", "" + bytes.length);
        response.setContentType("application/octet-stream");

        try (OutputStream toClient = new BufferedOutputStream(response.getOutputStream())) {
            toClient.write(bytes);
            toClient.flush();
        }
    }

    /**
     * 物理删除文件（含数据库记录）
     * @param id 文件记录ID（路径参数）
     * @return {@link Result} 操作结果对象
     * @implNote 实现流程：
     * 1. 查询文件记录 → 2. 删除本地文件 → 3. 删除数据库记录
     * @throws CustomException 当文件不存在或ID无效时抛出1001错误
     */
    @DeleteMapping("/{id}")
    public Result deleteFile(@PathVariable String id) {
        NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
        if (nxSystemFileInfo == null) {
            throw new CustomException("1001", "未查询到该文件");
        }

        FileUtil.del(new File(BASE_PATH + nxSystemFileInfo.getFileName()));
        nxSystemFileInfoService.delete(Long.parseLong(id));

        return Result.success();
    }

    /**
     * 获取文件元数据信息
     * @param id 文件记录ID（路径参数）
     * @return {@link Result} 包含文件信息的结果对象
     * @throws CustomException 当文件不存在或已被删除时抛出1001错误
     */
    @GetMapping("/{id}")
    public Result<NxSystemFileInfo> getById(@PathVariable String id) {
        NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
        if (nxSystemFileInfo == null) {
            throw new CustomException("1001", "数据库未查到此文件");
        }

        try {
            FileUtil.readBytes(BASE_PATH + nxSystemFileInfo.getFileName());
        } catch (Exception e) {
            throw new CustomException("1001", "此文件已被您删除");
        }
        return Result.success(nxSystemFileInfo);
    }

    /**
     * 计算文件流的SHA-256哈希值（工具方法）
     * @param inputStream 文件输入流
     * @return 十六进制字符串格式的哈希值
     * @throws IOException 文件读取异常
     * @throws NoSuchAlgorithmException 当SHA-256算法不可用时抛出运行时异常
     */
    private String calculateFileHash(InputStream inputStream) throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] buffer = new byte[8192];
            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }

            return Hex.encodeHexString(digest.digest());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256算法不可用", e);
        } finally {
            inputStream.close();
        }
    }
}